﻿using System;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Mathematics;
using FarseerGames.FarseerPhysics.Factories;
using FarseerGames.FarseerPhysics.Collisions;
using FarseerGames.FarseerPhysics;
using Microsoft.Xna.Framework.Graphics;
using Rectangle = Microsoft.Xna.Framework.Rectangle;

namespace CymbergajTouch.Match
{
    /// <summary>
    /// Pusher class.
    /// </summary>
    class Pusher : Movable
    {
        //Farseer
        public Body body;
        public Geom geom;
        protected float mass = 1.0f;
        public float radius;
        private float normalRadius;
        private float bigRadius;
        private float smallRadius;
        protected int radiusDenominator = 15;
        protected const int numberOfEdges = 60;
        private const float restitutionCoefficient = 0.9f;
        private bool grabbed = false;

        //Moving
        public Vector2 inputPosition;
        public Vector2 mouseOffset;
        protected float speed = 0.066f;

        //XNA
        protected Texture2D texture;

        //Game
        public bool left;

        public Pusher(PhysicsSimulator physicsSimulator, Vector2 position, bool? left)
        {
            this.radius = IceRink.iceHeight / radiusDenominator;
            this.normalRadius = radius;
            this.bigRadius = 2 * radius;
            this.smallRadius = radius / 2;
            this.body = CreateBody(position);
            physicsSimulator.Add(this.body);
            this.geom = CreateGeom();
            physicsSimulator.Add(this.geom);
            this.left = left == null ? true : (bool)left;
        }

        public Pusher(Vector2 position)
        {
            this.radius = IceRink.iceHeight / radiusDenominator;
            this.body = CreateBody(position);
            this.geom = CreateGeom();
        }

        /// <summary>
        /// Sets pusher's texture.
        /// </summary>
        /// <param name="texture">texture</param>
        public void SetTexture(Texture2D texture)
        {
            this.texture = texture;
        }

        /// <summary>
        /// Creates Farseer body object.
        /// </summary>
        /// <param name="position">initial position</param>
        /// <returns>created body object</returns>
        protected Body CreateBody(Vector2 position)
        {
            Body result = BodyFactory.Instance.CreateCircleBody(radius, mass);
            result.Position = position;
            return result;
        }

        /// <summary>
        /// Creates Farseer geom object.
        /// </summary>
        /// <returns>created geom object</returns>
        protected Geom CreateGeom()
        {
            Geom result = GeomFactory.Instance.CreateCircleGeom(body, radius, numberOfEdges);
            result.RestitutionCoefficient = restitutionCoefficient;
            return result;
        }

        public bool IsTouched(Vector2 position)
        {
            float dx = body.Position.X - position.X;
            float dy = body.Position.Y - position.Y;
            double distance = Math.Sqrt(dx * dx + dy * dy);
            return distance <= radius;
        }

        public void Grab(Vector2 inputPosition)
        {
            grabbed = true;
            this.inputPosition = inputPosition;
            this.mouseOffset = new Vector2(inputPosition.X - this.body.Position.X, inputPosition.Y - this.body.Position.Y);
            this.body.IsStatic = false;
        }

        public void Move(Vector2 newPosition)
        {
            this.body.IsStatic = false;
            newPosition = BoundPosition(newPosition);
            this.inputPosition = newPosition;
            float deltaX = newPosition.X - mouseOffset.X - body.Position.X;
            float deltaY = newPosition.Y - mouseOffset.Y - body.Position.Y;
            body.LinearVelocity = new Vector2(deltaX * this.speed, deltaY * this.speed);
        }

        public void Drop()
        {
            grabbed = false;
            this.body.IsStatic = false;
            this.body.LinearVelocity = new Vector2(0, 0);
        }

        public void ClearVector()
        {
            if (!grabbed)
            {
                return;
            }
            float dx = Math.Abs(this.body.Position.X + this.mouseOffset.X - this.inputPosition.X);
            float dy = Math.Abs(this.body.Position.Y + this.mouseOffset.Y - this.inputPosition.Y);
            if (dx <= 1 && dy <= 1)
            {
                this.body.LinearVelocity = new Vector2(0, 0);
                this.body.Rotation = 0.0f;
                this.body.IsStatic = true;
            }
        }

        /// <summary>
        /// Bounds position of input signal.
        /// </summary>
        /// <param name="position">signal position</param>
        /// <returns>bounded position</returns>
        protected Vector2 BoundPosition(Vector2 position)
        {
            //another bad smell, maybe shadowing can solve it somehow...
            if (this is Puck)
            {
                return position;
            }

            //colliding with bounds
            if (!left)
            {
                if (position.X < IceRink.iceWidth / 2 + radius + mouseOffset.X)
                {
                    position.X = IceRink.iceWidth / 2 + radius + mouseOffset.X;
                }
            }
            else
            {
                if (position.X < radius + mouseOffset.X)
                {
                    position.X = radius + mouseOffset.X;
                }
            }
            if (left)
            {
                if (position.X > IceRink.iceWidth / 2 - radius + mouseOffset.X)
                {
                    position.X = IceRink.iceWidth / 2 - radius + mouseOffset.X;
                }
            }
            else
            {
                if (position.X > IceRink.iceWidth - radius + mouseOffset.X)
                {
                    position.X = IceRink.iceWidth - radius + mouseOffset.X;
                }
            }
            if (position.Y < radius + mouseOffset.Y)
            {
                position.Y = radius + mouseOffset.Y;
            }
            if (position.Y > IceRink.iceHeight - radius + mouseOffset.Y)
            {
                position.Y = IceRink.iceHeight - radius + mouseOffset.Y;
            }

            return position;
        }

        /// <summary>
        /// Renders pusher's object.
        /// </summary>
        /// <param name="spritaBatch">XNA Sprite Batch</param>
        public void Draw(SpriteBatch spriteBatch)
        {
            Rectangle destinationRectangle = new Rectangle((int)(geom.Position.X - geom.AABB.Width / 2),
                (int)(geom.Position.Y - geom.AABB.Height / 2),
                (int)geom.AABB.Width, (int)geom.AABB.Height);
            spriteBatch.Draw(texture, destinationRectangle, Color.White);
        }

        /// <summary>
        /// Makes pusher big.
        /// </summary>
        public void Enlarge()
        {
            radius = bigRadius;
            Vector2 scale = new Vector2(bigRadius / normalRadius, bigRadius / normalRadius);
            this.geom.LocalVertices.Scale(ref scale);
        }

        /// <summary>
        /// Makes pusher small.
        /// </summary>
        public void Reduce()
        {
            radius = smallRadius;
            Vector2 scale = new Vector2(smallRadius / normalRadius, smallRadius / normalRadius);
            this.geom.LocalVertices.Scale(ref scale);
        }

        /// <summary>
        /// Makes pusher's size normal.
        /// </summary>
        public void Normalize()
        {
            Vector2 scale = new Vector2(normalRadius / radius, normalRadius / radius);
            radius = normalRadius;
            this.geom.LocalVertices.Scale(ref scale);
        }
    }
}
